﻿// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using Microsoft.Internal;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;

namespace System.ComponentModel.Composition.Hosting
{
    public partial class CompositionBatch
    {
        private object _lock = new object();
        private bool _copyNeededForAdd;
        private bool _copyNeededForRemove;
        private List<ComposablePart> _partsToAdd;
        private ReadOnlyCollection<ComposablePart> _readOnlyPartsToAdd;
        private List<ComposablePart> _partsToRemove;
        private ReadOnlyCollection<ComposablePart> _readOnlyPartsToRemove;

        /// <summary>
        /// Initializes a new instance of the <see cref="CompositionBatch"/> class.
        /// </summary>
        public CompositionBatch() : 
            this(Enumerable.Empty<ComposablePart>(),Enumerable.Empty<ComposablePart>())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CompositionBatch"/> class.
        /// </summary>
        /// <param name="partsToAdd">The parts to add.</param>
        /// <param name="partsToRemove">The parts to remove.</param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="partsToAdd"/> is <see langword="null"/>.
        ///     <para>
        ///         -or-
        ///     </para>
        ///     <paramref name="partsToRemove"/> is <see langword="null"/>.
        /// </exception>
        public CompositionBatch(IEnumerable<ComposablePart> partsToAdd, IEnumerable<ComposablePart> partsToRemove)
        {
            Requires.NotNull(partsToAdd, "partsToAdd");
            Requires.NotNull(partsToRemove, "partsToRemove");

            this._partsToAdd = partsToAdd.ToList();
            this._readOnlyPartsToAdd = this._partsToAdd.AsReadOnly();

            this._partsToRemove = partsToRemove.ToList();
            this._readOnlyPartsToRemove = this._partsToRemove.AsReadOnly();
        }

        /// <summary>
        /// Returns the collection of parts that will be added.
        /// </summary>
        /// <value>The parts to be added.</value>
        public ReadOnlyCollection<ComposablePart> PartsToAdd
        {
            get
            {
                lock (this._lock)
                {
                    this._copyNeededForAdd = true;
                    return this._readOnlyPartsToAdd;
                }
            }
        }

        /// <summary>
        /// Returns the collection of parts that will be removed.
        /// </summary>
        /// <value>The parts to be removed.</value>
        public ReadOnlyCollection<ComposablePart> PartsToRemove
        {
            get
            {
                lock (this._lock)
                {
                    this._copyNeededForRemove = true;
                    return this._readOnlyPartsToRemove;
                }
            }
        }

        /// <summary>
        ///     Adds the specified part to the <see cref="CompositionBatch"/>.
        /// </summary>
        /// <param name="part">
        /// The part.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="part"/> is <see langword="null"/>.
        /// </exception>
        public void AddPart(ComposablePart part)
        {
            Requires.NotNull(part, "part");
            lock (this._lock)
            {
                if (this._copyNeededForAdd)
                {
                    this._partsToAdd = new List<ComposablePart>(this._partsToAdd);
                    this._readOnlyPartsToAdd = this._partsToAdd.AsReadOnly();
                    this._copyNeededForAdd = false;
                }
                this._partsToAdd.Add(part);
            }
        }

        /// <summary>
        ///     Removes the specified part from the <see cref="CompositionBatch"/>.
        /// </summary>
        /// <param name="part">
        /// The part.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="part"/> is <see langword="null"/>.
        /// </exception>
        public void RemovePart(ComposablePart part)
        {
            Requires.NotNull(part, "part");
            lock (this._lock)
            {
                if (this._copyNeededForRemove)
                {
                    this._partsToRemove = new List<ComposablePart>(this._partsToRemove);
                    this._readOnlyPartsToRemove = this._partsToRemove.AsReadOnly();
                    this._copyNeededForRemove = false;
                }
                this._partsToRemove.Add(part);
            }
        }

        /// <summary>
        ///     Adds the specified object to the <see cref="CompositionBatch"/>, exporting it under a contract 
        ///     name derived from the specified type parameter.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of the object to add. The contract name is also derived from this type 
        ///     parameter.
        /// </typeparam>
        /// <param name="exportedObject">
        ///     The <see cref="object"/> to add to the <see cref="CompositionBatch"/>. This can
        ///     be <see langword="null"/>.
        /// </param>
        /// <returns>
        ///     A <see cref="ComposablePart"/> that can be used remove the <see cref="object"/>
        ///     from the <see cref="CompositionBatch"/>.
        /// </returns>
        /// <remarks>
        ///     <para>
        ///         The contract name is the result of calling 
        ///         <see cref="CompositionServices.GetContractName(Type)"/> on <typeparamref name="T"/>.
        ///     </para>
        /// </remarks>
        public ComposablePart AddExportedObject<T>(T exportedObject)
        {
            string contractName = CompositionServices.GetContractName(typeof(T));

            return this.AddExportedObject(contractName, exportedObject);
        }

        /// <summary>
        ///     Adds the specified object to the <see cref="CompositionBatch"/>, exporting it under the specified 
        ///     contract name.
        /// </summary>
        /// <param name="contractName">
        ///     A <see cref="String"/> containing the contract name to export the <see cref="object"/> 
        ///     under.
        /// </param>
        /// <param name="exportedObject">
        ///     The <see cref="object"/> to add to the <see cref="CompositionBatch"/>. This can
        ///     be <see langword="null"/>.
        /// </param>
        /// <returns>
        ///     A <see cref="ComposablePart"/> that can be used remove the <see cref="Object"/>
        ///     from the <see cref="CompositionBatch"/>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="contractName"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="contractName"/> is an empty string ("").
        /// </exception>
        /// <remarks>
        /// </remarks>
        /// 
        [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "object")]
        public ComposablePart AddExportedObject(string contractName, object exportedObject)
        {
            return this.AddExport(new Export(contractName, () => exportedObject));
        }

        /// <summary>
        ///     Adds the specified export to the <see cref="CompositionBatch"/>.
        /// </summary>
        /// <param name="export">
        ///     The <see cref="Export"/> to add to the <see cref="CompositionBatch"/>.
        /// </param>
        /// <returns>
        ///     A <see cref="ComposablePart"/> that can be used remove the <see cref="Export"/>
        ///     from the <see cref="CompositionBatch"/>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="export"/> is <see langword="null"/>.
        /// </exception>
        /// <remarks>
        /// </remarks>
        public ComposablePart AddExport(Export export)
        {
            Requires.NotNull(export, "export");

            ComposablePart part = new SingleExportComposablePart(export);

            this.AddPart(part);

            return part;
        }

        /// <summary>
        ///     Adds the specified attributed composable part to the <see cref="CompositionBatch"/>.
        /// </summary>
        /// <param name="attributedPart">
        ///     An <see cref="Object"/> containing the attributed composable part to add to the 
        ///     <see cref="CompositionBatch"/>.
        /// </param>
        /// <returns>
        ///     A <see cref="ComposablePart"/> that can be used remove the composable part from
        ///     the <see cref="CompositionBatch"/>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="attributedPart"/> is <see langword="null"/>.
        /// </exception>
        /// <remarks>
        ///     <para>
        ///         This method is equivalent to passing <paramref name="attributedPart"/> to 
        ///         <see cref="CompositionServices.CreateAttributedPart"/> and passing the created 
        ///         instance to <see cref="AddPart(ComposablePart)"/>. 
        ///     </para>
        /// </remarks>
        public ComposablePart AddPart(object attributedPart)
        {
            Requires.NotNull(attributedPart, "attributedPart");

            ComposablePart part = CompositionServices.CreateAttributedPart(attributedPart);

            this.AddPart(part);

            return part;
        }
    }
}
